using OpenTK.Mathematics;
using System;

namespace AssetImporter
{
    public interface ICamera
    {
        Matrix4 ProjMatrix{ get; }
        Matrix4 ViewMatrix{ get; }
        void OnResize(int width, int height);
        void Reset(Vector3 position, Vector3 target);
        void LookAt(in Vector3 target);
        void MoveTo(in Vector3 pos);
        void SurrendTarget(in Vector2 xy);
        void SurrendCamera(in Vector2 xy);
        void Move(in Vector2 xy);
        void MoveIn(float d);
    }
    class CameraSurrendTargetOrder : IOrder
    {
        public static ICamera? camera { private get; set; }
        readonly Vector2 xy;
        public CameraSurrendTargetOrder(Vector2 xy) { this.xy = xy; }
        public void Do() { camera?.SurrendTarget(xy); }
    }
    class CameraSurrendCameraOrder : IOrder
    {
        public static ICamera? camera { private get; set; }
        readonly Vector2 xy;
        public CameraSurrendCameraOrder(Vector2 xy) { this.xy = xy; }
        public void Do() { camera?.SurrendCamera(xy); }
    }
    class CameraMoveOrder : IOrder
    {
        public static ICamera? camera { private get; set; }
        readonly Vector2 xy;
        public CameraMoveOrder(Vector2 xy) { this.xy = xy; }
        public void Do() { camera?.Move(xy); }
    }
    class CameraMoveInOrder : IOrder
    {
        public static ICamera? camera { private get; set; }
        readonly float d;
        public CameraMoveInOrder(float d) { this.d = d; }
        public void Do() { camera?.MoveIn(d); }
    }
    public abstract class Camera : ICamera
    {
        internal int width;
        internal int height;
        internal Vector3 position;
        internal Vector3 target;
        internal Vector3 up;
        internal Matrix4 proj;
        internal Matrix4 view;
        float xSpeed = 2.0f;
        float ySpeed = 2.0f;
        public Matrix4 ProjMatrix => proj;
        public Matrix4 ViewMatrix => view;
        public Camera(int width, int height, Vector3 position, Vector3 target)
        {
            OnResize(width, height);
            Reset(position, target);
        }

        public abstract void OnResize(int width, int height);
        // {
        //     this.width = width;
        //     this.height = height;

        //     Matrix4.CreatePerspectiveFieldOfView(45f * 3.14f / 180f, (float)width / (float)height, 0.01f, 100f, out proj);
        // }

        public void Reset(Vector3 position, Vector3 target)
        {
            this.position = position;
            this.target = target;

            var front = (target - position);
            front.Normalize();
            Vector3 right;
            if(front.X == 0 && front.Y == 0)
            {
                right = new Vector3(1, 0, 0);
            }
            else
            {
                right = new Vector3(front.Y, -front.X, 0);
                right.Normalize();
            }
            up = Vector3.Cross(right, front);
            view = Matrix4.LookAt(position, target, up);
        }
        public void LookAt(in Vector3 target)
        {
            Reset(position, target);
        }
        public void MoveTo(in Vector3 pos)
        {
            Reset(pos, target);
        }

        public void SurrendTarget(in Vector2 xy)
        {
            var front = target - position;
            var dis = front.Length;
            front.Normalize();
            var right = Vector3.Cross(front, up);
            Matrix3.CreateFromAxisAngle(up, xy.X / width * xSpeed / 2 / 3.14f, out var r1);
            Matrix3.CreateFromAxisAngle(right, -xy.Y / height * ySpeed / 2 / 3.14f, out var r2);
            front = r1 * r2 * front;
            up = r1 * r2 * up;
            position = target - dis * front;
            view = Matrix4.LookAt(position, target, up);
        }

        public void SurrendCamera(in Vector2 xy)
        {
            var front = target - position;
            var dis = front.Length;
            front.Normalize();
            var right = Vector3.Cross(front, up);
            Matrix3.CreateFromAxisAngle(up, -xy.X / width * xSpeed / 2 / 3.14f, out var r1);
            Matrix3.CreateFromAxisAngle(right, xy.Y / height * ySpeed / 2 / 3.14f, out var r2);
            front = r1 * r2 * front;
            up = r1 * r2 * up;
            target = position + dis * front;
            view = Matrix4.LookAt(position, target, up);
        }

        public void Move(in Vector2 xy)
        {
            var front = target - position;
            var dis = front.Length;
            front.Normalize();
            var right = Vector3.Cross(front, up);
            position = position - xy.X / width * xSpeed * right - xy.Y / width * ySpeed * up;
            target = target - xy.X / width * xSpeed * right - xy.Y / width * ySpeed * up;
            view = Matrix4.LookAt(position, target, up);
        }

        public abstract void MoveIn(float d);
        // {
        //     var front = target - position;
        //     var dis = front.Length;
        //     front.Normalize();
        //     position = target - front * d / dis;
        //     view = Matrix4.LookAt(position, target, up);
        // }
    }   

    public class PerspectiveCamera : Camera
    {
        public PerspectiveCamera(int width, int height, Vector3 position, Vector3 target) : base(width, height, position, target) {}
        public override void OnResize(int width, int height)
        {
            this.width = width;
            this.height = height;

            Matrix4.CreatePerspectiveFieldOfView(45f * 3.14f / 180f, (float)width / (float)height, 0.01f, 100f, out proj);
        }
        public override void MoveIn(float d)
        {
            var front = target - position;
            var dis = front.Length;
            if (dis > 100 && d < 0)
                return;
            if (dis < 0.1 && d > 0)
                return;
            front.Normalize();
            position = position + front * d * dis;
            view = Matrix4.LookAt(position, target, up);
        }
    }

    public class OrthoCamera : Camera
    {
        float scale = 100f;
        float zoomSpeed = 100f;
        public OrthoCamera(int width, int height, Vector3 position, Vector3 target) : base(width, height, position, target) {}
        public override void OnResize(int width, int height)
        {
            this.width = width;
            this.height = height;

            Matrix4.CreateOrthographic(width / scale, height / scale, 0.1f, 100f, out proj);
        }
        public override void MoveIn(float d)
        {
            if (d == 0)
                return;
            if (scale < 5f && d < 0)
                return;
            if (scale > 1000f && d > 0)
                return;
            scale = scale - (float)Math.Pow(zoomSpeed, -d) + 1;
            Matrix4.CreateOrthographic(width / scale, height / scale, 0.1f, 100f, out proj);
        }

    }

}